/*
 * $Id: IPTDSListener.java,v 1.16 2009/10/22 22:24:55 afaichun Exp $
 * 
 * Copyright (C) 2009 General Electric Company. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of the General
 * Electric Company (GE). You shall not disclose this software and shall use it
 * only in accordance with the terms of the license agreement you entered into
 * with GE.
 * 
 * GE MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR
 * NON-INFRINGEMENT. GE SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING, OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES.
 */
package com.ge.healthcare.autosc.processor.iptds;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

import org.springframework.util.Assert;

import com.ge.healthcare.autosc.common.ASCLogger;
import com.ge.healthcare.autosc.common.database.dao.IRunDetailsDAO;
import com.ge.healthcare.autosc.common.dbLogger.StepLogger;
import com.ge.healthcare.autosc.processor.exception.IPTDSListenerException;
import com.ge.healthcare.autosc.processor.exception.IPTDSSocketException;
import com.ge.healthcare.autosc.processor.iptds.worker.IPTDSClient;
import com.ge.healthcare.autosc.processor.iptds.worker.IPTDSThreadPoolManager;
/**
 * 
 * @author 212042946
 *
 */
public class IPTDSListener implements Runnable {

	private IRunDetailsDAO iRunDetails = null;
	private IPTDSProperties iptdsProperties = null;
	private String processName = null;
	private IPTDSThreadPoolManager threadPoolManager;
	private volatile boolean stop = false;
	private Thread listener = null;
	private Selector selector;
	/*
	 * Constructor 
	 */
	public IPTDSListener(String stepTypeName) throws IOException {
		processName = stepTypeName + "." + StepLogger.getManagedServerName() +
			"." + getProcessType();
		selector = Selector.open();
	}
	
	/**
	 * Get the name of this process class
	 * @return process class
	 */
	public static String getProcessType() {
		return "IPTDSListener";
	}
	
	/**
	 * Run the IPTDS listener. If this method is used in Spring as 
	 * init-method property, it interrupts the Spring life cycle
	 * by using the class before Post-initialization state.
	 */
	public void init() {
		Assert.notNull(iRunDetails, 
				"ActRunDetailDAO is not defined. Use setRunDetailsDAO to provide one");
		Assert.notNull(iptdsProperties,
				"IPTDSProperties is not defined. Use setIPTDSProperties to provide one");
		Assert.notNull(threadPoolManager,
			"IPTDSThreadPoolManager is not defined. Use setIPTDSThreadPoolManager to provide one");
		listener = new Thread(this, processName);
		listener.start();
	}
	
	/**
	 * Stop the IPTDSListener. In Spring, this is used as customized destroy 
	 * method to clean up resources.
	 */
	public void stop() {
		ASCLogger.debug("Stop IPTDSListener...");
		stop = true;
		selector.wakeup();
	}

	/**
	 * Main running loop. Create server socket channel and start
	 * select loop.
	 */
	public void run() {
		String methodName = "run";
		SelectionKey selKey = null;
		
		ServerSocketChannel ssChannel = null;
		ASCLogger.info("IPTDSListener start running");
		while(!stop) {
			try {
				try {
					ssChannel = ServerSocketChannel.open();
					ssChannel.configureBlocking(false);
					ssChannel.socket().bind(new InetSocketAddress(iptdsProperties.getListenPort()));
					// If you add more key, make sure you add handler below
					ssChannel.register(this.selector, SelectionKey.OP_ACCEPT);
					while(!stop) {
						// register IPTDS Listener
						iRunDetails.saveRunDetails(processName);
			
						// check if we have any more thread first before start listening.
						if(threadPoolManager.anyWorker() == false) {
							ASCLogger.debug(this.getClass(), methodName, "No worker, sleep a while...");
							try {
								Thread.sleep(100);
							}
							catch(InterruptedException ie) {
								ASCLogger.warn(this.getClass(), "run", "Interruption while sleep for available worker.", ie);
								Thread.interrupted();
							}
							continue;
						}
						
						int n=0;
						n = this.selector.select();
						if(n > 0) {
							Iterator<SelectionKey> iter = this.selector.selectedKeys().iterator();
							while(iter.hasNext()) {
								selKey = (SelectionKey)iter.next();
								// remove the key
								iter.remove();
				
								if(selKey.isValid() && selKey.isAcceptable()) {
									SocketChannel sChannel = ssChannel.accept();
									ASCLogger.info("IPTDS request comes in...");
									handleRequest(sChannel.socket());
								}
							}
						}
					}
				} catch (IOException ioe) {
					String error = new StringBuffer()
								.append("IOException during IPTDS communication.\n")
								.append("Listening port: ")
								.append(Integer.valueOf(iptdsProperties.getListenPort()).toString()).append("\n")
								.append("IPTDSListener is shutting down..").toString();
					ASCLogger.warn(this.getClass(), methodName, error.toString(), new IPTDSSocketException(ioe));
					// Throw this for FTR purpose
					throw new IPTDSListenerException(error, ioe);
				}
			} catch (IPTDSListenerException iptdsle) {
				try {
					Thread.sleep(500);
				} catch (InterruptedException ie) {
					ASCLogger.warn(this.getClass(), "run", 
							"Interruption while sleeping and before reconstructing the server socket.", ie);
					Thread.interrupted();
				}
				closeChannel(ssChannel);
			}
		}

		// stop accepting more connection request
		try {
			this.selector.close();
			closeChannel(ssChannel);
		} catch (IOException ioe) {
			ASCLogger.warn(this.getClass(), "run", new StringBuffer()
				.append("Error closing server socket.\n")
				.append("Listening port: ")
			    .append(iptdsProperties.getListenPort()).toString(),
			    ioe);
		}
		// shutdown the executor
		threadPoolManager.stop();
		ASCLogger.info("IPTDSListener stopped");
	}
	
	/**
	 * Set runDetailsDAO
	 * @param runDetailsDAO
	 */
	public void setRunDetailsDAO(IRunDetailsDAO runDetailsDAO) {
		this.iRunDetails = runDetailsDAO;
	}
	
	/**
	 * Set IPTDS Properties
	 * @param iptdsProperties
	 */
	public void setIPTDSProperties(IPTDSProperties iptdsProperties) {
		this.iptdsProperties = iptdsProperties;
	}
	
	/**
	 * Set IPTDSThreadPoolManager
	 * @param threadPoolManager
	 */
	public void setIPTDSThreadPoolManager(IPTDSThreadPoolManager threadPoolManager) {
		this.threadPoolManager = threadPoolManager;
	}
	
	private void closeChannel(ServerSocketChannel ssChannel) {
		try {
			if((ssChannel != null) && (ssChannel.isOpen())) {
				ssChannel.close();
			}
		} catch (IOException ioe) {
			ASCLogger.warn(this.getClass(), "closeChannel", new StringBuffer()
						   .append("Error closing server socket.\n")
						   .append("Listening port: ")
						   .append(Integer.valueOf(iptdsProperties.getListenPort())).toString(),
						   ioe);
		} 
	}
	
	/*
	 * Call Thread pool manager to handle incoming request
	 */
	private void handleRequest(Socket socket) {
		StringBuffer contactInfo = new StringBuffer()
								.append("Local IP: ").append(socket.getLocalAddress()).append("\n")
								.append("Local Port: "). append(socket.getLocalPort()).append("\n")
								.append("Remote IP: ").append(socket.getRemoteSocketAddress());
		try {
			ASCLogger.info(this.getClass(), "handleRequest", 
				"Call worker to handle IPTDS transactions.\n" + contactInfo.toString());
			socket.setSoTimeout(iptdsProperties.getSocketTimeout());
			IPTDSClient worker = new IPTDSClient(socket, threadPoolManager, iptdsProperties);
			threadPoolManager.execute(worker);
		} catch (SocketException se) {
			ASCLogger.error(this.getClass(), "handleRequest", 
    				"Error from established socket to IPTDS client.\n" + contactInfo.toString());
			try {
				if((socket != null) && (!socket.isClosed())) {
					socket.close();
				}
			} catch (IOException ioe) {
				ASCLogger.warn(this.getClass(), "handleRequest", 
						"Failed to close client socket", ioe);
			}
		}
	}
}
